package cn.thornbird.orgsync.helper;

import cn.thornbird.orgsync.entity.CertificateDetail;
import cn.thornbird.orgsync.entity.User;
import cn.thornbird.orgsync.entity.status.AppType;
import cn.thornbird.orgsync.helper.converter.DingTalkConverter;
import cn.thornbird.orgsync.helper.converter.FeishuConverter;
import cn.thornbird.orgsync.helper.converter.NeteaseConverter;
import cn.thornbird.orgsync.helper.converter.WechatConverter;
import cn.thornbird.orgsync.model.*;
import cn.thornbird.orgsync.model.combined.TaskObject;
import cn.thornbird.orgsync.model.dingtalk.DepartmentId;
import cn.thornbird.orgsync.model.feishu.DepartmentDetail;
import cn.thornbird.orgsync.model.netease.UnitItemData;
import cn.thornbird.orgsync.model.wechat.IdResponse;
import cn.thornbird.orgsync.service.CertificateService;
import cn.thornbird.orgsync.util.DateUtil;
import cn.thornbird.orgsync.util.EncryptUtil;
import cn.thornbird.orgsync.util.JsonUtil;
import cn.thornbird.orgsync.util.UserIdUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.parameters.P;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

public class OrganizationSynchronization implements Loggable {

    public static final String DELIMITER = "\r\n\r\n";

    public static final String DEFAULT_PASSWORD = "Abc-1234";

    private CertificateService certificateService;

    private StringRedisTemplate redisTemplate;

    private TaskObject taskObject;

    private StringBuffer logs;

    private boolean hasError;

    private AppHelper sourceHelper;

    private AppHelper targetHelper;

    private String defaultPassword;

    private Map<String, AppUserData> sourceEmployees;

    private Map<String, AppUserData> targetEmployees;

    private Map<String, List<String>> oldEmployeeDepartments;

    private Map<String, List<String>> newEmployeeDepartments;

    private Map<String, Boolean> updateEmployeeCache;

    public OrganizationSynchronization(CertificateService certificateService, StringRedisTemplate redisTemplate,
                                       TaskObject taskObject) {
        this.certificateService = certificateService;
        this.redisTemplate = redisTemplate;
        this.taskObject = taskObject;
        logs = new StringBuffer();
        hasError = false;
    }

    public boolean runTask() {
        boolean success = false;
        try {
            success = run();
        } catch (Exception e) {
            String errorMessage = getStackTraceInfo(e);
            writeLog(true, "系统错误: [code]{0}[/code]", errorMessage);
        }
        return success;
    }

    @Override
    public String getLog() {
        String log = logs.toString();
        if (log.endsWith(DELIMITER)) {
            log = logs.substring(0, logs.length() - DELIMITER.length());
        }
        return log.isEmpty() ? null : log;
    }

    @Override
    public String toJsonString(Object object) {
        return String.format("[code]%s[/code]", JsonUtil.toJsonString(object, true, false));
    }

    @Override
    public void writeLog(boolean error, String log) {
        logs.append(DateUtil.formatDate(new Date(), DateUtil.DATETIME_FORMAT));
        logs.append(" ");
        logs.append(error ? "[ERROR]" : "[INFO]");
        logs.append(" ");
        logs.append(log);
        logs.append(DELIMITER);
        hasError = error ? true : hasError;
    }

    @Override
    public void writeLog(boolean error, String log, Object... arguments) {
        log = MessageFormat.format(log, arguments);
        writeLog(error, log);
    }

    private boolean run() throws Exception {
        String source = String.format("%s / %s / %s", taskObject.getSourceCompany().getName(),
                taskObject.getTask().getSourceAppType(), taskObject.getTask().getSourceDepartmentName());
        String target = String.format("%s / %s / %s", taskObject.getTargetCompany().getName(),
                taskObject.getTask().getTargetAppType(), taskObject.getTask().getTargetDepartmentName());
        writeLog(false, "开始同步任务, ID: {0}, 来源: [{1}], 目标: [{2}], 包含子部门: {3}, 包含雇员: {4}",
                taskObject.getTask().getId(), source, target,
                taskObject.getTask().getIncludingChildren(),
                taskObject.getTask().getIncludingEmployees());

        UserData userData = getUserData();
        List<TreeData> sourceTreeDataList;
        TreeData sourceDepartment;
        List<TreeData> targetTreeDataList;
        TreeData targetDepartment;

        CertificateDetail sourceCertificateDetail = getCertificateDetail(userData,
                taskObject.getTask().getSourceCompanyId(),
                taskObject.getTask().getSourceAppType());
        if (sourceCertificateDetail == null) {
            return false;
        }
        writeLog(false, "获取来源组织架构");
        sourceHelper = AppHelper.getHelper(taskObject.getTask().getSourceAppType());
        sourceHelper.initialize(redisTemplate, sourceCertificateDetail.getData());
        sourceTreeDataList = sourceHelper.getOrganizationTree();
        sourceDepartment = findDepartment(sourceTreeDataList, taskObject.getTask().getSourceDepartmentId());
        if (sourceDepartment == null) {
            writeLog(true, "获取来源部门失败, 找不到指定ID的部门");
            return false;
        }
        writeLog(false, "获取来源部门: {0}", toJsonString(sourceDepartment));

        CertificateDetail targetCertificateDetail = getCertificateDetail(userData,
                taskObject.getTask().getTargetCompanyId(),
                taskObject.getTask().getTargetAppType());
        if (targetCertificateDetail == null) {
            return false;
        }
        writeLog(false, "获取目标组织架构");
        targetHelper = AppHelper.getHelper(taskObject.getTask().getTargetAppType());
        targetHelper.initialize(redisTemplate, targetCertificateDetail.getData());
        targetTreeDataList = targetHelper.getOrganizationTree();
        targetDepartment = findDepartment(targetTreeDataList, taskObject.getTask().getTargetDepartmentId());
        if (targetDepartment == null) {
            writeLog(true, "获取目标部门失败, 找不到指定ID的部门");
            return false;
        }
        writeLog(false, "获取目标部门: {0}", toJsonString(targetDepartment));

        sourceHelper.setRequestLogger(this);
        targetHelper.setRequestLogger(this);
        defaultPassword = taskObject.getTask().getDefaultPassword();
        defaultPassword = StringUtils.isEmpty(defaultPassword) ? DEFAULT_PASSWORD : defaultPassword;
        sourceEmployees = new HashMap<>();
        targetEmployees = new HashMap<>();
        oldEmployeeDepartments = new HashMap<>();
        newEmployeeDepartments = new HashMap<>();
        updateEmployeeCache = new HashMap<>();
        hasError = false;
        writeLog(false, "开始同步组织架构");
        syncDepartment(sourceDepartment, targetDepartment,
                taskObject.getTask().getIncludingChildren(),
                taskObject.getTask().getIncludingEmployees(),
                taskObject.getTask().getDeleteNonExistentChildren());
        restoreEmployeeDepartments();
        sourceHelper.setRequestLogger(null);
        targetHelper.setRequestLogger(null);
        sourceEmployees.clear();
        targetEmployees.clear();
        oldEmployeeDepartments.clear();
        newEmployeeDepartments.clear();
        updateEmployeeCache.clear();
        writeLog(false, "同步组织架构结束");
        return !hasError;
    }

    private CertificateDetail getCertificateDetail(UserData userData, String companyId, AppType appType) {
        CertificateDetail certificateDetail = null;
        try {
            certificateDetail = certificateService.findDetail(userData, companyId, appType);
        } catch (Exception e) {
            writeLog(true, "获取同步凭证明细失败, 公司ID: {0}, 应用类型: {1}, 错误信息: {2}",
                    companyId, appType, e.getMessage());
        }
        return certificateDetail;
    }

    private UserData getUserData() {
        User user = new User();
        List<Resource> resources = new ArrayList<>();
        resources.add(new Resource(UserData.AUTHORIZED));
        user.setId(taskObject.getTask().getUserId());
        user.setUsername(taskObject.getTask().getCreatedBy());
        return new UserData(user, resources);
    }

    private TreeData findDepartment(List<TreeData> treeDataList, String departmentId) {
        TreeData department = null;
        if (treeDataList != null) {
            for (TreeData treeData : treeDataList) {
                if (treeData.getKey().equals(departmentId)) {
                    department = treeData;
                    break;
                } else if (treeData.getChildren() != null) {
                    department = findDepartment(treeData.getChildren(), departmentId);
                    if (department != null) {
                        break;
                    }
                }
            }
        }
        return department;
    }

    private TreeData findDepartmentByTitle(List<TreeData> treeDataList, String title) {
        TreeData department = null;
        if (treeDataList != null) {
            for (TreeData treeData : treeDataList) {
                if (treeData.getTitle().equalsIgnoreCase(title)) {
                    department = treeData;
                    break;
                }
            }
        }
        return department;
    }

    private void syncDepartment(TreeData sourceDepartment, TreeData targetDepartment,
                                Boolean includingChildren, Boolean includingEmployees,
                                Boolean deleteNonExistentChildren) {
        if (targetDepartment == null) {
            return;
        }

        if (sourceDepartment.getChildren() == null) {
            sourceDepartment.setChildren(new ArrayList<>());
        }
        Set<String> departmentIdSet = new HashSet<>();
        int order = 0;
        boolean createDepartmentError = false;
        for (TreeData treeData : sourceDepartment.getChildren()) {
            order++;
            String departmentName = treeData.getTitle();
            writeLog(false, "同步部门: [{0}]", departmentName);
            TreeData department = findDepartmentByTitle(targetDepartment.getChildren(), departmentName);
            if (department == null) {
                department = createDepartment(departmentName, targetDepartment.getKey(), order,
                        sourceDepartment.getKey());
                if (department != null) {
                    if (targetDepartment.getChildren() == null) {
                        targetDepartment.setChildren(new ArrayList<>());
                    }
                    targetDepartment.getChildren().add(department);
                    writeLog(false, "成功创建创建部门: [{0}], 部门ID: {1}", departmentName, department.getKey());
                } else {
                    writeLog(true, "创建部门失败: [{0}]", departmentName);
                    createDepartmentError = true;
                }
            }
            if (department != null) {
                if (updateDepartmentOrder(department.getKey(), order)) {
                    writeLog(false, "成功更新部门顺序: [{0}], 部门ID: {1}", departmentName, department.getKey());
                } else {
                    writeLog(true, "更新部门顺序失败: [{0}], 部门ID: {1}", departmentName, department.getKey());
                }
            }
            if (department != null) {
                departmentIdSet.add(department.getKey());
            }
            if (treeData.getChildren() != null && includingChildren != null && includingChildren.booleanValue()) {
                syncDepartment(treeData, department, includingChildren, includingEmployees, deleteNonExistentChildren);
            }
        }

        if (!createDepartmentError && deleteNonExistentChildren != null && deleteNonExistentChildren.booleanValue()) {
            List<TreeData> children = targetDepartment.getChildren();
            children = children == null ? new ArrayList<>() : children;
            for (TreeData treeData : children) {
                if (!departmentIdSet.contains(treeData.getKey())) {
                    writeLog(false, "删除来源部门不存在的子部门: [{0}]", treeData.getTitle());
                    if (deleteDepartment(treeData.getKey())) {
                        writeLog(false, "成功删除部门: [{0}], 部门ID: {1}", treeData.getTitle(), treeData.getKey());
                    } else {
                        writeLog(true, "删除部门失败: [{0}], 部门ID: {1}", treeData.getTitle(), treeData.getKey());
                    }
                }
            }
        }

        if (includingEmployees != null && includingEmployees.booleanValue()) {
            writeLog(false, "开始同步部门雇员: [{0}]", sourceDepartment.getTitle());
            syncEmployee(sourceDepartment, targetDepartment);
            writeLog(false, "同步部门雇员结束: [{0}]", sourceDepartment.getTitle());
            for (TreeData treeData : sourceDepartment.getChildren()) {
                if (treeData.getChildren() == null || treeData.getChildren().isEmpty()) {
                    TreeData targetChild = null;
                    List<TreeData> children = targetDepartment.getChildren();
                    children = children == null ? new ArrayList<>() : children;
                    for (TreeData child : children) {
                        if (child.getTitle().equals(treeData.getTitle())) {
                            targetChild = child;
                            break;
                        }
                    }
                    if (targetChild != null) {
                        writeLog(false, "开始同步部门雇员: [{0}]", treeData.getTitle());
                        syncEmployee(treeData, targetChild);
                        writeLog(false, "同步部门雇员结束: [{0}]", treeData.getTitle());
                    }
                }
            }
        }
    }

    private void syncEmployee(TreeData sourDepartment, TreeData targetDepartment) {
        if (targetHelper.currentAppType == AppType.DingTalk) {
            ((DingTalkHelper) targetHelper).getAllUsers();
        }
        List<AppUserData> sourceUsers = sourceHelper.getUsersByDepartmentId(sourDepartment.getKey());
        List<AppUserData> targetUsers = targetHelper.getUsersByDepartmentId(targetDepartment.getKey());
        for (AppUserData user : sourceUsers) {
            sourceEmployees.put(user.getId(), user);
            boolean isLeader = false;
            if (user.getDepartments() != null && user.getDepartments().containsKey(sourDepartment.getKey()) &&
                    user.getDepartments().get(sourDepartment.getKey()).getLeader() != null &&
                    user.getDepartments().get(sourDepartment.getKey()).getLeader()) {
                isLeader = true;
            }
            AppUserData targetUser = targetHelper.findAppUser(user);
            if (targetUser == null) {
                createEmployee(user, targetDepartment, isLeader, true);
            } else {
                updateEmployee(user, targetUser, targetDepartment, isLeader);
            }
        }
        for (AppUserData user : targetUsers) {
            AppUserData sourceUser = sourceHelper.findAppUser(sourceUsers, user);
            if (sourceUser == null) {
                removeEmployee(user, targetDepartment);
            } else {
                boolean isLeader = false;
                if (sourceUser.getDepartments() != null &&
                        sourceUser.getDepartments().containsKey(sourDepartment.getKey()) &&
                        sourceUser.getDepartments().get(sourDepartment.getKey()).getLeader() != null &&
                        sourceUser.getDepartments().get(sourDepartment.getKey()).getLeader()) {
                    isLeader = true;
                }
                updateEmployee(sourceUser, user, targetDepartment, isLeader);
            }
        }
    }

    private void restoreEmployeeDepartments() {
        writeLog(false, "开始将根组织结构下或默认部门下的雇员移除");
        for (String key : newEmployeeDepartments.keySet()) {
            if (oldEmployeeDepartments.containsKey(key)) {
                List<String> departmentIds = calculateDepartmentIds(oldEmployeeDepartments.get(key),
                        newEmployeeDepartments.get(key));
                if (departmentIds != null) {
                    updateEmployeeDepartments(key, departmentIds, true);
                }
            }
        }
        writeLog(false, "根组织结构下或默认部门下的雇员移除结束");
    }

    private TreeData createDepartment(String departmentName, String parentId, int order, String sourceDepartmentId) {
        TreeData department = null;
        String departmentId = null;
        switch (targetHelper.currentAppType) {
            case Netease:
                NeteaseHelper neteaseHelper = (NeteaseHelper) targetHelper;
                UnitItemData unitItemData = neteaseHelper.createUnit(parentId.equals("0") ? null : parentId,
                        departmentName, null);
                if (unitItemData != null && unitItemData.isSuccess()) {
                    departmentId = unitItemData.getData().getUnitId();
                    neteaseHelper.updateUnitRank(departmentId, (long) order);
                }
                break;
            case Wechat:
                WechatHelper wechatHelper = (WechatHelper) targetHelper;
                IdResponse idResponse = wechatHelper.createDepartment(departmentName, null,
                        Long.parseLong(parentId), null, (long) order);
                if (idResponse != null && idResponse.isSuccess()) {
                    departmentId = idResponse.getId().toString();
                }
                break;
            case DingTalk:
                DingTalkHelper dingTalkHelper = (DingTalkHelper) targetHelper;
                DepartmentId departmentIdResponse = dingTalkHelper.createDepartment(departmentName,
                        Long.parseLong(parentId), sourceDepartmentId, (long) order, false);
                if (departmentIdResponse != null && departmentIdResponse.isSuccess()) {
                    departmentId = departmentIdResponse.getResult().getDeptId().toString();
                }
                break;
            case Feishu:
                FeishuHelper feishuHelper = (FeishuHelper) targetHelper;
                DepartmentDetail departmentDetail = feishuHelper.createDepartment(departmentName, null, parentId,
                        null, String.valueOf(order), null);
                if (departmentDetail != null && departmentDetail.isSuccess()) {
                    departmentId = departmentDetail.getData().getDepartment().getDepartmentId();
                }
                break;
            default:
                break;
        }
        if (departmentId != null) {
            department = new TreeData();
            department.setKey(departmentId);
            department.setTitle(departmentName);
        }
        return department;
    }

    private boolean deleteDepartment(String departmentId) {
        boolean success;
        IResponse response = null;
        switch (targetHelper.currentAppType) {
            case Netease:
                if (departmentId.endsWith("_default")) {
                    writeLog(false, "默认部门不可删除: {0}", departmentId);
                    success = true;
                    break;
                }
                NeteaseHelper neteaseHelper = (NeteaseHelper) targetHelper;
                response = neteaseHelper.deleteUnit(departmentId);
                break;
            case Wechat:
                WechatHelper wechatHelper = (WechatHelper) targetHelper;
                response = wechatHelper.deleteDepartment(Long.parseLong(departmentId));
                break;
            case DingTalk:
                DingTalkHelper dingTalkHelper = (DingTalkHelper) targetHelper;
                response = dingTalkHelper.deleteDepartment(Long.parseLong(departmentId));
                break;
            case Feishu:
                FeishuHelper feishuHelper = (FeishuHelper) targetHelper;
                response = feishuHelper.deleteDepartment(departmentId);
                break;
            default:
                break;
        }
        success = response != null && response.isSuccess();
        return success;
    }

    private boolean updateDepartmentOrder(String departmentId, int order) {
        boolean success;
        IResponse response = null;
        switch (targetHelper.currentAppType) {
            case Netease:
                NeteaseHelper neteaseHelper = (NeteaseHelper) targetHelper;
                response = neteaseHelper.updateUnitRank(departmentId, (long) order);
                break;
            case Wechat:
                WechatHelper wechatHelper = (WechatHelper) targetHelper;
                response = wechatHelper.updateDepartment(Long.parseLong(departmentId),
                        null, null, null, (long) order);
                break;
            case DingTalk:
                DingTalkHelper dingTalkHelper = (DingTalkHelper) targetHelper;
                response = dingTalkHelper.updateDepartment(Long.parseLong(departmentId), null, null, null,
                        (long) order, false);
                break;
            case Feishu:
                FeishuHelper feishuHelper = (FeishuHelper) targetHelper;
                String departmentOrder = StringUtils.leftPad(String.valueOf(order), 4, "0");
                response = feishuHelper.updateDepartment(departmentId, null, null, null,
                        departmentOrder, null, null, null);
                break;
            default:
                break;
        }
        success = response != null && response.isSuccess();
        return success;
    }

    private AppUserData createEmployee(AppUserData user, TreeData targetDepartment, boolean isLeader,
                                       boolean createLeaderFlag) {
        AppUserData targetUser = null;
        if (user == null) {
            return targetUser;
        }

        if (targetEmployees.containsKey(user.getId())) {
            targetUser = targetEmployees.get(user.getId());
            if (targetUser.getDepartments() == null) {
                targetUser.setDepartments(new HashMap<>());
            }
            if (targetDepartment != null && !targetUser.getDepartments().containsKey(targetDepartment.getKey())) {
                targetUser.getDepartments().put(targetDepartment.getKey(), new AppUserData.DepartmentInfo());
                List departmentIds = targetUser.getDepartments().keySet().stream().collect(Collectors.toList());
                updateEmployeeDepartments(targetUser.getId(), departmentIds);
            }
            return targetUser;
        }

        targetUser = targetHelper.findAppUser(user);
        if (targetUser == null) {
            if (createLeaderFlag) {
                List<AppUserData> leaders = findLeaders(user);
                for (AppUserData leader : leaders) {
                    createEmployee(leader, null, false, false);
                }
            }
            String leaderId = null;
            if (!StringUtils.isEmpty(user.getLeaderId()) && targetEmployees.containsKey(user.getLeaderId())) {
                leaderId = targetEmployees.get(user.getLeaderId()).getId();
            }
            targetUser = createEmployee(user, targetDepartment == null ? null : targetDepartment.getKey(), isLeader,
                    leaderId);
        } else {
            if (createLeaderFlag) {
                updateEmployee(user, targetUser, targetDepartment, isLeader);
            }
        }
        return targetUser;
    }

    private AppUserData createEmployee(AppUserData user, String departmentId, boolean isLeader, String leaderId) {
        AppUserData newUser = null;
        boolean pending = false;
        String userId = UserIdUtil.getUserId(user);
        Map<String, AppUserData.DepartmentInfo> departments = new HashMap<>();
        if (!StringUtils.isEmpty(departmentId)) {
            AppUserData.DepartmentInfo departmentInfo = new AppUserData.DepartmentInfo();
            departmentInfo.setLeader(isLeader);
            departments.put(departmentId, departmentInfo);
        }
        writeLog(false, "创建雇员 [{0}]: {1}", user.getName(), toJsonString(user));
        switch (targetHelper.currentAppType) {
            case Netease:
                NeteaseHelper neteaseHelper = (NeteaseHelper) targetHelper;
                NeteaseConverter neteaseConverter = new NeteaseConverter();
                userId = UserIdUtil.getEmailAccountName(user);
                cn.thornbird.orgsync.model.netease.AccountDetail neteaseResponse = neteaseHelper.createAccount(
                        neteaseConverter.toUserInfo(user).setId(userId)
                                .setDepartments(departments, null)
                                .setLeaderId(leaderId)
                                .setDefaultPassword(defaultPassword));
                if (neteaseResponse != null && neteaseResponse.isSuccess()) {
                    newUser = neteaseConverter.parseUser(neteaseResponse.getData());
                }
                break;
            case Wechat:
                WechatHelper wechatHelper = (WechatHelper) targetHelper;
                cn.thornbird.orgsync.model.wechat.Response wechatResponse = wechatHelper.createUser(
                        new WechatConverter().toUserInfo(user).setId(userId)
                                .setDepartments(departments, departmentId)
                                .setLeaderId(leaderId));
                if (wechatResponse != null && wechatResponse.isSuccess()) {
                    newUser = wechatHelper.findAppUserById(userId);
                }
                break;
            case DingTalk:
                DingTalkHelper dingTalkHelper = (DingTalkHelper) targetHelper;
                cn.thornbird.orgsync.model.dingtalk.Response dingTalkResponse = dingTalkHelper.createUser(
                        new DingTalkConverter().toUserInfo(user).setId(userId)
                                .setDepartments(departments, departmentId)
                                .setLeaderId(leaderId));
                if (dingTalkResponse != null && dingTalkResponse.isSuccess()) {
                    pending = cn.thornbird.orgsync.model.dingtalk.Response.isPending(dingTalkResponse);
                    if (!pending) {
                        newUser = dingTalkHelper.findAppUserById(userId);
                    }
                }
                break;
            case Feishu:
                FeishuHelper feishuHelper = (FeishuHelper) targetHelper;
                FeishuConverter feishuConverter = new FeishuConverter();
                cn.thornbird.orgsync.model.feishu.UserDetail feishuResponse = feishuHelper.createUser(
                        feishuConverter.toUserInfo(user).setId(userId)
                                .setDepartments(departments, departmentId)
                                .setLeaderId(leaderId)
                                .setDefaultEmployeeType());
                if (feishuResponse != null && feishuResponse.isSuccess()) {
                    newUser = feishuConverter.parseUser(feishuResponse.getData().getUser());
                }
                break;
            default:
                break;
        }
        if (newUser != null) {
            targetEmployees.put(user.getId(), newUser);
        } else if (!pending) {
            writeLog(true, "创建雇员失败");
        }
        return newUser;
    }

    private boolean removeEmployee(AppUserData user, TreeData targetDepartment) {
        boolean success = true;
        writeLog(false, "从部门移除雇员 [{0}]: {1}", user.getName(), toJsonString(user));
        if (user.getDepartments() != null && user.getDepartments().containsKey(targetDepartment.getKey())) {
            user.getDepartments().remove(targetDepartment.getKey());
            List<String> departmentIds = user.getDepartments().keySet().stream().collect(Collectors.toList());
            success = updateEmployeeDepartments(user.getId(), departmentIds);
        }
        return success;
    }

    private boolean updateEmployee(AppUserData sourceUser, AppUserData targetUser, TreeData targetDepartment,
                                   boolean isLeader) {
        boolean success = true;
        if (!oldEmployeeDepartments.containsKey(targetUser.getId())) {
            oldEmployeeDepartments.put(targetUser.getId(), targetUser.getDepartmentIds());
        }

        String leaderId = null;
        List<AppUserData> leaders = findLeaders(sourceUser);
        if (!leaders.isEmpty()) {
            AppUserData leaderUser = null;
            for (AppUserData leader : leaders) {
                AppUserData employee = createEmployee(leader, null, false, false);
                if (employee == null) {
                    success = false;
                    break;
                }
                if (leaderUser == null) {
                    leaderUser = employee;
                }
            }
            leaderId = leaderUser == null ? null : leaderUser.getId();
        }
        if (!success) {
            return success;
        }

        success = updateEmployee(sourceUser, targetUser, targetDepartment.getKey(), isLeader, leaderId);
        if (success) {
            targetUser.setLeaderId(leaderId);
            targetEmployees.put(sourceUser.getId(), targetUser);
        }
        return success;
    }

    private boolean updateEmployee(AppUserData sourceUser, AppUserData targetUser,
                                   String departmentId, boolean isLeader, String leaderId) {
        boolean success = false;
        if (targetUser.getDepartments() == null) {
            targetUser.setDepartments(new HashMap<>());
        }
        if (!targetUser.getDepartments().containsKey(departmentId)) {
            targetUser.getDepartments().put(departmentId, new AppUserData.DepartmentInfo());
        }
        targetUser.getDepartments().get(departmentId).setLeader(isLeader);
        writeLog(false, "更新雇员 [{0}]: {1}", sourceUser.getName(), toJsonString(sourceUser));
        String key = null;
        IResponse response = null;
        switch (targetHelper.currentAppType) {
            case Netease:
                NeteaseHelper neteaseHelper = (NeteaseHelper) targetHelper;
                cn.thornbird.orgsync.model.netease.AccountInfo neteaseUserInfo =
                        new NeteaseConverter().toUserInfo(sourceUser)
                                .setId(targetUser.getId())
                                .setDepartments(targetUser.getDepartments(), targetUser.getMainDepartmentId())
                                .setLeaderId(leaderId);
                key = getUpdateUserKey(neteaseUserInfo);
                success = updateEmployeeCache.containsKey(key);
                if (!success) {
                    response = neteaseHelper.updateAccount(neteaseUserInfo);
                }
                break;
            case Wechat:
                WechatHelper wechatHelper = (WechatHelper) targetHelper;
                cn.thornbird.orgsync.model.wechat.UserInfo wechatUserInfo =
                        new WechatConverter().toUserInfo(sourceUser)
                                .setId(targetUser.getId())
                                .setDepartments(targetUser.getDepartments(), targetUser.getMainDepartmentId())
                                .setLeaderId(leaderId);
                key = getUpdateUserKey(wechatUserInfo);
                success = updateEmployeeCache.containsKey(key);
                if (!success) {
                    wechatHelper.removeCircularReference(targetUser.getId(), leaderId);
                    response = wechatHelper.updateUser(wechatUserInfo);
                }
                break;
            case DingTalk:
                DingTalkHelper dingTalkHelper = (DingTalkHelper) targetHelper;
                cn.thornbird.orgsync.model.dingtalk.UserInfo dingTalkUserInfo =
                        new DingTalkConverter().toUserInfo(sourceUser)
                                .setId(targetUser.getId())
                                .setDepartments(targetUser.getDepartments(), targetUser.getMainDepartmentId())
                                .setLeaderId(leaderId);
                key = getUpdateUserKey(dingTalkUserInfo);
                success = updateEmployeeCache.containsKey(key);
                if (!success) {
                    dingTalkHelper.removeCircularReference(targetUser.getId(), leaderId);
                    response = dingTalkHelper.updateUser(dingTalkUserInfo);
                }
                break;
            case Feishu:
                FeishuHelper feishuHelper = (FeishuHelper) targetHelper;
                cn.thornbird.orgsync.model.feishu.UserInfo feishuUserInfo =
                        new FeishuConverter().toUserInfo(sourceUser)
                                .setId(targetUser.getId())
                            .setDepartments(targetUser.getDepartments(), targetUser.getMainDepartmentId())
                            .setLeaderId(leaderId);
                key = getUpdateUserKey(feishuUserInfo);
                success = updateEmployeeCache.containsKey(key);
                if (!success) {
                    feishuHelper.removeCircularReference(targetUser.getId(), leaderId);
                    response = feishuHelper.updateUser(feishuUserInfo);
                }
                break;
            default:
                break;
        }
        if (!success && response != null) {
            success = response.isSuccess();
        }
        if (success) {
            updateEmployeeCache.put(key, true);
        } else {
            writeLog(true, "更新雇员失败");
        }
        return success;
    }

    private boolean updateEmployeeDepartments(String id, List<String> departmentIds) {
        return updateEmployeeDepartments(id, departmentIds, false);
    }

    private boolean updateEmployeeDepartments(String id, List<String> departmentIds, boolean flag) {
        boolean success;
        IResponse response = null;
        switch (targetHelper.currentAppType) {
            case Netease:
                if (departmentIds.isEmpty()) {
                    departmentIds.add("default");
                }
                NeteaseHelper neteaseHelper = (NeteaseHelper) targetHelper;
                cn.thornbird.orgsync.model.netease.AccountInfo accountInfo;
                accountInfo = new cn.thornbird.orgsync.model.netease.AccountInfo();
                accountInfo.setAccountName(id);
                accountInfo.setUnitId(String.join(",", departmentIds));
                cn.thornbird.orgsync.model.netease.Response neteaseResponse = neteaseHelper.updateAccount(accountInfo);
                success = neteaseResponse != null && neteaseResponse.isSuccess();
                break;
            case Wechat:
                if (departmentIds.isEmpty()) {
                    departmentIds.add("1");
                }
                List<Long> department = departmentIds.stream().map(Long::parseLong).collect(Collectors.toList());
                WechatHelper wechatHelper = (WechatHelper) targetHelper;
                cn.thornbird.orgsync.model.wechat.UserInfo wechatUserInfo;
                wechatUserInfo = new cn.thornbird.orgsync.model.wechat.UserInfo();
                wechatUserInfo.setUserId(id);
                wechatUserInfo.setDepartment(department);
                departmentIds.stream().map(Long::parseLong).forEach(wechatUserInfo.getDepartment()::add);
                response = wechatHelper.updateUser(wechatUserInfo);
                break;
            case DingTalk:
                if (departmentIds.isEmpty()) {
                    departmentIds.add("1");
                }
                DingTalkHelper dingTalkHelper = (DingTalkHelper) targetHelper;
                cn.thornbird.orgsync.model.dingtalk.UserInfo dingTalkUserInfo;
                dingTalkUserInfo = new cn.thornbird.orgsync.model.dingtalk.UserInfo();
                dingTalkUserInfo.setUserId(id);
                dingTalkUserInfo.setDeptIdList(departmentIds);
                response = dingTalkHelper.updateUser(dingTalkUserInfo);
                break;
            case Feishu:
                if (departmentIds.isEmpty()) {
                    departmentIds.add("0");
                }
                FeishuHelper feishuHelper = (FeishuHelper) targetHelper;
                cn.thornbird.orgsync.model.feishu.UserInfo feishuUserInfo;
                feishuUserInfo = new cn.thornbird.orgsync.model.feishu.UserInfo();
                feishuUserInfo.setUserId(id);
                feishuUserInfo.setDepartmentIds(departmentIds);
                response = feishuHelper.updateUser(feishuUserInfo);
                break;
            default:
                break;
        }
        success = response != null && response.isSuccess();
        if (!success) {
            writeLog(true, "更新雇员部门失败");
        }
        if (success && !flag) {
            newEmployeeDepartments.put(id, departmentIds);
        }
        return success;
    }

    private List<String> calculateDepartmentIds(List<String> oldDepartmentIds, List<String> newDepartmentIds) {
        List<String> departmentIds = null;
        String newId = null;
        for (String id : newDepartmentIds) {
            switch (targetHelper.currentAppType) {
                case Netease:
                    if (id.endsWith("_default")) {
                        newId = id;
                    }
                    break;
                case Wechat:
                case DingTalk:
                    if (id.equals("1")) {
                        newId = id;
                    }
                    break;
                case Feishu:
                    if (id.equals("0")) {
                        newId = id;
                    }
                    break;
                default:
                    break;
            }
            if (newId != null) {
                break;
            }
        }
        if (newId != null && !oldDepartmentIds.contains(newId)) {
            departmentIds = new ArrayList<>();
            for (String id : newDepartmentIds) {
                if (!newId.equals(id)) {
                    departmentIds.add(id);
                }
            }
        }
        return departmentIds;
    }

    private List<AppUserData> findLeaders(AppUserData user) {
        List<AppUserData> leaders = new ArrayList<>();
        AppUserData employee = user;
        sourceEmployees.put(user.getId(), user);
        while (true) {
            if (!StringUtils.isEmpty(employee.getLeaderId())) {
                if (sourceEmployees.containsKey(employee.getLeaderId())) {
                    employee = sourceEmployees.get(employee.getLeaderId());
                } else {
                    employee = sourceHelper.findAppUserById(employee.getLeaderId());
                }
                if (employee != null) {
                    sourceEmployees.put(employee.getId(), employee);
                    leaders.add(employee);
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        return leaders;
    }

    private String getUpdateUserKey(Object userInfo) {
        return EncryptUtil.md5(JsonUtil.toJsonString(userInfo));
    }

    private String getStackTraceInfo(Exception exception) {
        String text;
        StringWriter stringWriter = null;
        PrintWriter printWriter = null;
        try {
            stringWriter = new StringWriter();
            printWriter = new PrintWriter(stringWriter);
            exception.printStackTrace(printWriter);
            printWriter.flush();
            stringWriter.flush();
            text = stringWriter.toString();
        } catch (Exception e) {
            text = exception.getMessage();
        } finally {
            if (stringWriter != null) {
                try {
                    stringWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (printWriter != null) {
                printWriter.close();
            }
        }
        return text;
    }

}
